/*
 * Copyright (C) 2008 Esmertec AG.
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.netease.financial.base.photo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;

import com.netease.financial.common.util.Closeables;
import com.netease.financial.common.util.NfsLog;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;


public class UriImage {

    private static final String TAG = UriImage.class.getSimpleName();

    private static final int NUMBER_OF_RESIZE_ATTEMPTS = 4;

    /**
     * The quality parameter which is used to compress PNG images.
     */
    public static final int IMAGE_COMPRESSION_QUALITY = 95;
    /**
     * The minimum quality parameter which is used to compress PNG images.
     */
    public static final int MINIMUM_IMAGE_COMPRESSION_QUALITY = 50;

    private final Context mContext;
    private final Uri mUri;
    private int mWidth;
    private int mHeight;

    public UriImage(Context context, Uri uri) {
        if ((null == context) || (null == uri)) {
            throw new IllegalArgumentException();
        }

        mContext = context;
        mUri = uri;

        decodeBoundsInfo();
    }

    private void decodeBoundsInfo() {
        InputStream input = null;
        try {
            input = mContext.getContentResolver().openInputStream(mUri);
            BitmapFactory.Options opt = new BitmapFactory.Options();
            opt.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(input, null, opt);
            mWidth = opt.outWidth;
            mHeight = opt.outHeight;
        } catch (FileNotFoundException e) {
            // Ignore
            NfsLog.e(TAG, "IOException caught while opening stream", e);
        } finally {
            Closeables.closeQuietly(input);
        }
    }

    /**
     * Get a version of this image resized to fit the given dimension and byte-size limits.
     *
     * @param widthLimit The width limit, in pixels
     * @param heightLimit The height limit, in pixels
     * @param byteLimit The binary size limit, in bytes
     * @return A resized/recompressed version of this image, in JPEG format
     */
    public byte[] getResizedImageData(int widthLimit, int heightLimit, int byteLimit) {
        return getResizedImageData(mWidth, mHeight, widthLimit, heightLimit, byteLimit, mUri, mContext);
    }

    /**
     * Resize and recompress the image such that it fits the given limits. The resulting byte
     * array contains an image in JPEG format, regardless of the original image's content type.
     * @param widthLimit The width limit, in pixels
     * @param heightLimit The height limit, in pixels
     * @param byteLimit The binary size limit, in bytes
     * @return A resized/recompressed version of this image, in JPEG format
     */
    public static byte[] getResizedImageData(int outWidth, int outHeight,
            int widthLimit, int heightLimit, int byteLimit, Uri uri, Context context) {

        float scaleFactor = 1.F;
        while ((outWidth * scaleFactor > widthLimit) || (outHeight * scaleFactor > heightLimit)) {
            scaleFactor *= .75F;
        }

        InputStream input = null;
        ByteArrayOutputStream os = null;
        try {
            int attempts = 1;
            int sampleSize = 1;
            BitmapFactory.Options options = new BitmapFactory.Options();
            int quality = IMAGE_COMPRESSION_QUALITY;
            Bitmap b = null;

            // In this loop, attempt to decode the stream with the best possible subsampling (we
            // start with 1, which means no subsampling - get the original content) without running
            // out of memory.
            do {
                input = context.getContentResolver().openInputStream(uri);
                options.inSampleSize = sampleSize;
                try {
                    b = BitmapFactory.decodeStream(input, null, options);
                    if (b == null) {
                        return null;    // Couldn't decode and it wasn't because of an exception,
                        // bail.
                    }
                } catch (OutOfMemoryError e) {
                    NfsLog.w(TAG, "getResizedBitmap: img too large to decode (OutOfMemoryError), " +
                            "may try with larger sampleSize. Curr sampleSize=" + sampleSize);
                    sampleSize *= 2;    // works best as a power of two
                    attempts++;
                } finally {
                    Closeables.closeQuietly(input);
                }
            } while (b == null && attempts < NUMBER_OF_RESIZE_ATTEMPTS);

            if (b == null) {
                if (attempts >= NUMBER_OF_RESIZE_ATTEMPTS) {
                    NfsLog.v(TAG, "getResizedImageData: gave up after too many attempts to resize");
                }
                return null;
            }

            boolean resultTooBig;
            attempts = 1;   // reset count for second loop
            // In this loop, we attempt to compress/resize the content to fit the given dimension
            // and file-size limits.
            do {
                try {
                    if (options.outWidth > widthLimit || options.outHeight > heightLimit ||
                            (os != null && os.size() > byteLimit)) {
                        // The decoder does not support the inSampleSize option.
                        // Scale the bitmap using Bitmap library.
                        int scaledWidth = (int)(outWidth * scaleFactor);
                        int scaledHeight = (int)(outHeight * scaleFactor);

                        NfsLog.v(TAG, "getResizedImageData: retry scaling using " +
                                "Bitmap.createScaledBitmap: w=" + scaledWidth +
                                ", h=" + scaledHeight);
                        b = Bitmap.createScaledBitmap(b, scaledWidth, scaledHeight, false);
                    }

                    if (b == null) {
                        NfsLog.d(TAG, "Bitmap.createScaledBitmap returned NULL!");
                        return null;
                    }
                    // Compress the image into a JPG. Start with IMAGE_COMPRESSION_QUALITY.
                    // In case that the image byte size is still too large reduce the quality in
                    // proportion to the desired byte size.
                    Closeables.closeQuietly(os);
                    os = new ByteArrayOutputStream();
                    b.compress(Bitmap.CompressFormat.JPEG, quality, os);
                    int jpgFileSize = os.size();
                    if (jpgFileSize > byteLimit) {
                        quality = (quality * byteLimit) / jpgFileSize;  // watch for int division!
                        if (quality < MINIMUM_IMAGE_COMPRESSION_QUALITY) {
                            quality = MINIMUM_IMAGE_COMPRESSION_QUALITY;
                        }

                        NfsLog.d(TAG, "getResizedImageData: compress(2) w/ quality=" + quality);

                        Closeables.closeQuietly(os);
                        os = new ByteArrayOutputStream();
                        b.compress(Bitmap.CompressFormat.JPEG, quality, os);
                    }
                } catch (java.lang.OutOfMemoryError e) {
                    NfsLog.w(TAG, "getResizedImageData - image too big (OutOfMemoryError), will try "
                            + " with smaller scale factor, cur scale factor: " + scaleFactor);
                    // fall through and keep trying with a smaller scale factor.
                }

                scaleFactor *= .75F;
                attempts++;
                resultTooBig = os == null || os.size() > byteLimit;
            } while (resultTooBig && attempts < NUMBER_OF_RESIZE_ATTEMPTS);

            if (b == null) {
                return null;
            }
            b.recycle();        // done with the bitmap, release the memory

            if (resultTooBig) {
                NfsLog.d(TAG, "getResizedImageData returning NULL because the result is too big.");
            }

            return resultTooBig ? null : os.toByteArray();
        } catch (FileNotFoundException e) {
            NfsLog.e(TAG, e.getMessage(), e);
            return null;
        }  catch (java.lang.OutOfMemoryError e) {
            NfsLog.e(TAG, e.getMessage(), e);
            return null;
        } finally {
            Closeables.closeQuietly(input);
            Closeables.closeQuietly(os);
        }
    }

}
